﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace LightXMachine
{
    public class LightManager
    {
        public List<LightSource> lightSources;
        public List<OcclusionBox> occList;
        public Color ambientLight;
        private RenderTarget2D lightmap;
        private Texture2D alphaTexture;

        /// <summary>
        /// A system for managing lights and shadows dynamically. Light sources should inherit
        /// from the LightSource class and be added to the lightSources list here.
        /// Boxes used for occlusion, i.e. creating shadows, should inherit from the OcclusionBox
        /// class and be added to the occList here.
        /// For the alphaTexture something along the lines of the included AlphaOne.dds should be used.
        /// </summary>
        /// <param name="graphicsDevice">The graphics device in use.</param>
        /// <param name="alphaTexture">Preferably a .dds file used for clearing the alpha channel.</param>
        /// <param name="ambientLight">The background lighting colour.</param>
        public LightManager(GraphicsDevice graphicsDevice, Texture2D alphaTexture, Color ambientLight)
        {
            OcclusionBox.InitializeStaticMembers(graphicsDevice);
            this.alphaTexture = alphaTexture;
            PresentationParameters pp = graphicsDevice.PresentationParameters;
            lightmap = new RenderTarget2D(graphicsDevice, pp.BackBufferWidth,
                pp.BackBufferHeight, true, SurfaceFormat.Color, DepthFormat.Depth24Stencil8,
                pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
        }

        /// <summary>
        /// Draws a light map on the screen. Note that the SpriteBatch must
        /// be turned off (aka not between Begin() and End()).
        /// </summary>
        /// <param name="spriteBatch">The SpriteBatch used.</param>
        /// <param name="graphicsDevice">The Graphics Device used.</param>
        /// <param name="screenPos">The ??</param>
        public void DrawLightmap(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice,
            Vector3 screenPos)
        {
            graphicsDevice.SetRenderTarget(lightmap);

            //clear to some small ambientLight light
            graphicsDevice.Clear(ambientLight);

            //graphicsDevice.VertexDeclaration = VertexPositionColor.VertexDeclaration;
            OcclusionBox.drawingEffect.World = Matrix.Identity;
            //REMOVED WHEN BUILDING MACHINE.
            //OcclusionBox.drawingEffect.Begin();
            OcclusionBox.drawingEffect.CurrentTechnique.Passes[0].Apply();

            //would like to just call this here, but it causes bugs
            //clear alpha to 1
            //ClearAlphaToOne();

            foreach (LightSource light in lightSources)
                CalcLight(light, spriteBatch, graphicsDevice, screenPos);
            //foreach (Dynamic light in lightSourcesPassive)
            //    CalcLight(light, sBatch, screenPos);
            //REMOVED WHEN BUILDING MACHINE.
            //OcclusionBox.drawingEffect.CurrentTechnique.Passes[0].End();
            //OcclusionBox.drawingEffect.End();

            //clear alpha, to avoid messing stuff up later. Probably not needed since
            //the rest of the graphics are done using the standard spritebatch.
            ClearAlphaToOne(spriteBatch, graphicsDevice);
            graphicsDevice.SetRenderTarget(null);
        }

        /// <summary>
        /// Clears the alpha channel, writing the alphaTexture to it.
        /// </summary>
        private void ClearAlphaToOne(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
        {
            //graphicsDevice.RenderState.ColorWriteChannels = ColorWriteChannels.Alpha;
            graphicsDevice.BlendState.ColorWriteChannels = ColorWriteChannels.Alpha;
            spriteBatch.Begin(0, BlendState.Opaque);
            spriteBatch.Draw(alphaTexture,
                new Rectangle(0, 0, graphicsDevice.Viewport.Width,
                    graphicsDevice.Viewport.Height), Color.White);
            spriteBatch.End();
            //GraphicsDevice.RenderState.ColorWriteChannels = ColorWriteChannels.All;
            graphicsDevice.BlendState.ColorWriteChannels = ColorWriteChannels.All;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="light"></param>
        /// <param name="spriteBatch"></param>
        /// <param name="graphicsDevice"></param>
        /// <param name="screenPos"></param>
        private void CalcLight(LightSource light, SpriteBatch spriteBatch,
            GraphicsDevice graphicsDevice, Vector3 screenPos)
        {
            //clear alpha to 1
            ClearAlphaToOne(spriteBatch, graphicsDevice);

            //draw all shadows
            //write only to the alpha channel, which sets alpha to 0
            //GraphicsDevice.RenderState.ColorWriteChannels = ColorWriteChannels.Alpha;
            graphicsDevice.BlendState.ColorWriteChannels = ColorWriteChannels.Alpha;
            //GraphicsDevice.RenderState.CullMode = CullMode.None;
            graphicsDevice.RasterizerState.CullMode = CullMode.None;
            //GraphicsDevice.RenderState.AlphaBlendEnable = true;
            graphicsDevice.BlendState = BlendState.AlphaBlend;
            //GraphicsDevice.RenderState.DestinationBlend = Blend.Zero;
            //GraphicsDevice.RenderState.SourceBlend = Blend.One;

            graphicsDevice.DepthStencilState = DepthStencilState.DepthRead;

            //Old stuff
            //if (OccBoxx.drawingEffect == null)
            //    OccBoxx.InitializeStaticMembers(GraphicsDevice);

            //Rectangle relevantArea;
            //if (light.left)
            //    relevantArea = new Rectangle((int)position.X, (int)position.Y, (int)(light.Center.X - position.X), CameraSquare.Height);
            //else
            //    relevantArea = new Rectangle((int)light.Center.X, (int)position.Y, (int)(CameraSquare.Right - light.Center.X), CameraSquare.Height);

            Vector2 origin = FindLightOrigin(light);

            foreach (OcclusionBox ob in occList)
            //if ((light.left && ob.cell.X <= light.cell.X + 1)
            //|| (!light.left && ob.cell.X >= light.cell.X - 1))
            {
                //if (light.canMoveFlashlight)
                ob.DetermineShadow(light, origin, screenPos);

                //ONLY USED WITH OLD VERSION OF DETERMINESHADOW()
                //ob.TranslateShadows(screenPos);

                //Old stuff
                //GraphicsDevice.VertexDeclaration = OccBoxx.vertexDecl;
                //OccBoxx.drawingEffect.World = Matrix.Identity;
                //OccBoxx.drawingEffect.Begin();
                //OccBoxx.drawingEffect.CurrentTechnique.Passes[0].Begin();

                graphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip,
                ob.TranslateShadows(light.id, screenPos), 0, ob.shadowVertexCount * 2 - 2);

                //Old stuff
                //OccBoxx.drawingEffect.CurrentTechnique.Passes[0].End();
                //OccBoxx.drawingEffect.End();
            }

            //graphicsDevice.RenderState.ColorWriteChannels = ColorWriteChannels.All;
            graphicsDevice.BlendState.ColorWriteChannels = ColorWriteChannels.All;

            //draw the light shape
            //where Alpha is 0, nothing will be written
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
            //GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            //GraphicsDevice.RenderState.SourceBlend = Blend.DestinationAlpha;
            graphicsDevice.BlendState.AlphaSourceBlend = Blend.DestinationAlpha;
            //GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;
            //GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = true;

            if (light.left)
                spriteBatch.Draw(light.lightTexture,
                    light.lightPosition - new Vector2(screenPos.X, screenPos.Y),
                    null, light.lightTint,
                    light.lightAngle,
                    new Vector2(light.lightTexture.Width * 0.5f, light.lightTexture.Height * 0.5f),
                    light.lightScale, SpriteEffects.None, light.lightDepth);
            else
                spriteBatch.Draw(light.lightTexture,
                    light.lightPosition - new Vector2(screenPos.X, screenPos.Y),
                    null, light.lightTint,
                    light.lightAngle + (MathHelper.PiOver2 - light.lightAngle) * 2,
                    new Vector2(light.lightTexture.Width * 0.5f, light.lightTexture.Height * 0.5f),
                    light.lightScale, SpriteEffects.None, light.lightDepth);
            spriteBatch.End();
        }

        /// <summary>
        /// Finds and returns the origin of the light source.
        /// </summary>
        /// <param name="light"></param>
        /// <returns></returns>
        private Vector2 FindLightOrigin(LightSource light)
        {
            if (light.left)
                return new Vector2((float)(light.lightPosition.X - light.lightDist *
                    Math.Cos(MathHelper.TwoPi - light.lightAngle)),
                    (float)(light.lightPosition.Y + light.lightDist *
                    Math.Sin(MathHelper.TwoPi - light.lightAngle)));
            else
                return new Vector2((float)(light.lightPosition.X + light.lightDist *
                    Math.Cos(light.lightAngle)),
                    (float)(light.lightPosition.Y - light.lightDist *
                    Math.Sin(light.lightAngle)));
        }

        #region Supplementary methods
        /*
        public bool LitTest(BasicObject target)
        {
            foreach (LightSource lightSource in lightSources)
            {
                if (lightSource.activator)
                {
                    bool abortLitTest = false;
                    //BasicObject target = GridHandler.theList[id];
                    Vector2 light = lightSource.Center;

                    Vector2[] rays = new Vector2[3];

                    if (lightSource.climbing)
                    {
                        rays[0] = new Vector2((float)(lightSource.Center.X - 600 * Math.Cos(lightSource.flashlightAngle + MathHelper.PiOver2 - MathHelper.ToRadians(10))),
                            (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle + MathHelper.PiOver2 - MathHelper.ToRadians(10))))
                            - light;
                        //Straight
                        rays[1] = new Vector2((float)(lightSource.Center.X - 600 * Math.Cos(lightSource.flashlightAngle + MathHelper.PiOver2)),
                            (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle + MathHelper.PiOver2)))
                            - light;
                        rays[2] = new Vector2((float)(lightSource.Center.X - 600 * Math.Cos(lightSource.flashlightAngle + MathHelper.PiOver2 + MathHelper.ToRadians(10))),
                            (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle + MathHelper.PiOver2 + MathHelper.ToRadians(10))))
                            - light;
                    }
                    else
                    {
                        if (lightSource.left)
                        {
                            rays[0] = new Vector2((float)(lightSource.Center.X - 600 * Math.Cos(lightSource.flashlightAngle - MathHelper.ToRadians(10))),
                                (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle - MathHelper.ToRadians(10))))
                                - light;
                            //Straight
                            rays[1] = new Vector2((float)(lightSource.Center.X - 600 * Math.Cos(lightSource.flashlightAngle)),
                                (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle)))
                                - light;
                            rays[2] = new Vector2((float)(lightSource.Center.X - 600 * Math.Cos(lightSource.flashlightAngle + MathHelper.ToRadians(10))),
                                (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle + MathHelper.ToRadians(10))))
                                - light;
                        }
                        else
                        {
                            rays[0] = new Vector2((float)(lightSource.Center.X + 600 * Math.Cos(lightSource.flashlightAngle - MathHelper.ToRadians(10))),
                                (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle - MathHelper.ToRadians(10))))
                                - light;
                            //Straight
                            rays[1] = new Vector2((float)(lightSource.Center.X + 600 * Math.Cos(lightSource.flashlightAngle)),
                                (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle)))
                                - light;
                            rays[2] = new Vector2((float)(lightSource.Center.X + 600 * Math.Cos(lightSource.flashlightAngle + MathHelper.ToRadians(10))),
                                (float)(lightSource.Center.Y - 600 * Math.Sin(lightSource.flashlightAngle + MathHelper.ToRadians(10))))
                                - light;
                        }
                    }
                    Vector2[,] rayPoints = new Vector2[rays.Length, 20];
                    bool interSectsTarget = false;
                    int interPoint = 0;
                    int interRay = 0;

                    for (int r = 0; r < rayPoints.GetLength(0) && !interSectsTarget; ++r)
                        for (int i = 0; i < rayPoints.GetLength(1) && !interSectsTarget; ++i)
                        {
                            rayPoints[r, i] = rays[r] * (i + 1) * 1 / rayPoints.GetLength(1);

                            if (CollisionHandler.PosIntersect(light + rayPoints[r, i], target))
                            {
                                interSectsTarget = true;
                                interPoint = i;
                                interRay = r;
                            }
                        }

                    if (interSectsTarget)
                    {
                        if (lightSource.climbing && light.Y > target.Center.Y)
                        {
                            foreach (OccBoxx ob in oList)
                            {
                                for (int i = 0; i < interPoint && !abortLitTest; ++i)
                                {
                                    if (CollisionHandler.PosIntersect(light + rayPoints[interRay, i], ob))
                                        abortLitTest = true;
                                }
                                if (abortLitTest)
                                    break;
                            }
                            if (!abortLitTest)
                                return true;
                        }
                        else
                        {
                            if (lightSource.left && light.X > target.Center.X)
                            {
                                foreach (OccBoxx ob in oList)
                                {
                                    for (int i = 0; i < interPoint && !abortLitTest; ++i)
                                    {
                                        if (CollisionHandler.PosIntersect(light + rayPoints[interRay, i], ob))
                                            abortLitTest = true;
                                    }
                                    if (abortLitTest)
                                        break;
                                }
                                if (!abortLitTest)
                                    return true;
                            }
                            else if (!lightSource.left && light.X < target.Center.X)
                            {
                                foreach (OccBoxx ob in oList)
                                {
                                    for (int i = 0; i < interPoint && !abortLitTest; ++i)
                                    {
                                        if (CollisionHandler.PosIntersect(light + rayPoints[interRay, i], ob))
                                        {
                                            abortLitTest = true;
                                        }
                                    }
                                    if (abortLitTest)
                                        break;
                                }
                                if (!abortLitTest)
                                    return true;
                            }
                        }
                    }
                }
            }
            return false;
        }
         * */
        #endregion //Supplementary methods
    }
}